package org.extremengine;

import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.extremengine.collision.AfterCollision;
import org.extremengine.collision.CollisionDetector;
import org.extremengine.collision.RelationGraph.Relation;
import org.extremengine.collision.RelationGraph.TraverseCallback;
import org.extremengine.common.ActiveBody;
import org.extremengine.common.Point;
import org.extremengine.common.RigidBody;
import org.extremengine.common.Terrain;
import org.extremengine.common.Velocity;
import org.extremengine.shape.HorizontalLine;
import org.extremengine.shape.LineSegment;
import org.extremengine.shape.Shape;
import org.extremengine.shape.UnlimitedLine;
import org.extremengine.shape.VerticalLine;

/**
 * 物理世界是物理引擎的主程序入口。World is the world.
 * 
 * @author yuxing
 * 
 */
public class World {

	// 最小帧时间（单位毫秒），默认为10，即最大FPS=100。Minimum time(ms) for a frame。
	public static final int MIN_FRAME_TIME = 10;

	private static final int START_TIME = 0;

	private TimerThread timer = new TimerThread();
	private TimerTask task;
	private Calendar pausedTime = Calendar.getInstance();
	
	// 
	private double gravityAccelaration = -9.8;
	
	// No less than 0.01, otherwise it will not work well for gravity acceleration affect. 
	private double speedValve = 0.1;
	
	// unit is meter.
	private double width = 80;
	private double height = 60;

	private CollisionDetector collisionDetector;
	
	private Set<Terrain> terrainSet = new HashSet<Terrain>();
	
	private Map<Object, RigidBody> bodyMap = new HashMap<Object, RigidBody>();

	/**
	 * 创建物理世界。
	 */
	public World() {
		super();
		collisionDetector = new CollisionDetector(this);
	}

	/**
	 * 创建指定宽度和高度的世界。
	 * @param width
	 * @param height
	 */
	public World(double width, double height) {
		this();
		this.width = width;
		this.height = height;
	}

	/**
	 * 创建指定宽度和高度的世界，并设定重力加速度。
	 * @param gravityAccelaration 重力加速度，无重力状态设为0
	 * @param width
	 * @param height
	 */
	public World(int gravityAccelaration, double width, double height) {
		this();
		this.gravityAccelaration = gravityAccelaration;
		this.width = width;
		this.height = height;
	}

	/**
	 * 创建无限长的水平地面（给定y坐标），并设定地面摩擦系数和弹性系数。
	 * @param y
	 * @param friction
	 * @param elasticity
	 * @return
	 */
	public Terrain createGround(double y, double friction, double elasticity) {
		Terrain t = new Terrain("Ground_" + terrainSet.size(), friction, elasticity, new HorizontalLine(y));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 创建有限长的水平地面（给定起止x坐标和y坐标），并设定地面摩擦系数和弹性系数。
	 * @param x1
	 * @param x2
	 * @param y
	 * @param friction
	 * @param elasticity
	 * @return
	 */
	public Terrain createGround(double x1, double x2, double y, double friction, double elasticity) {
		Terrain t = new Terrain("Ground_" + terrainSet.size(), friction, elasticity, new LineSegment(
				new Point(x1, y), new Point(x2, y)));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 创建有限长的水平天花板（给定y坐标），并设定天花板摩擦系数和弹性系数。
	 * @param y
	 * @param friction
	 * @param elasticity
	 * @return
	 */
	public Terrain createCeiling(double y, double friction, double elasticity) {
		Terrain t = new Terrain("Ceiling_" + terrainSet.size(), friction, elasticity,
				new HorizontalLine(y, UnlimitedLine.FREE_SPACE_NEGETIVE));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 创建有限长的水平天花板（给定起止x坐标和y坐标），并设定天花板摩擦系数和弹性系数。
	 * @param x1
	 * @param x2
	 * @param y
	 * @param friction
	 * @param elasticity
	 * @return
	 */
	public Terrain createCeiling(double x1, double x2, double y, double friction, double elasticity) {
		Terrain t = new Terrain("Ceiling_" + terrainSet.size(), friction, elasticity, new LineSegment(
				new Point(x1, y), new Point(x2, y)));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 创建无限长的墙面（给定x坐标），设定墙面摩擦系数和弹性系数
	 * @param x
	 * @param friction
	 * @param elasticity
	 * @param freeSpace
	 * @return
	 */
	public Terrain createWall(double x, double friction, double elasticity, int freeSpace) {
		Terrain t = new Terrain("Wall_" + terrainSet.size(), friction, elasticity, new VerticalLine(x, freeSpace));
		terrainSet.add(t);
		return t;
	}

	public Terrain createWall(double x, double y1, double y2, double friction, double elasticity) {
		Terrain t = new Terrain("Wall_" + terrainSet.size(), friction, elasticity, new LineSegment(
				new Point(x, y1), new Point(x, y2)));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 在物理世界中创建一个悬浮于空中的岛。
	 * @param x1
	 * @param x2
	 * @param y1
	 * @param y2
	 * @param friction
	 * @param elasticity
	 * @return
	 */
	public Terrain createIsland(double x1, double x2, double y1, double y2, double friction,
			double elasticity) {
		Terrain t = new Terrain("Island_" + terrainSet.size(), friction, elasticity, new LineSegment(
				new Point(x1, y1), new Point(x2, y2)));
		terrainSet.add(t);
		return t;
	}

	/**
	 * 在物理世界中创建一个刚体。
	 * @param targetObj 与刚体关联的用户对象
	 * @param friction 摩擦系数，取值0至1之间，0为无摩擦
	 * @param elasticity 弹性系数，取值0至1之间，越大碰撞后损失的动能越小，0为完全非弹性碰撞，1为完全弹性碰撞，此参数与相碰撞的刚体共同决定碰后刚体的速度。
	 * @param mass 刚体的质量，单位是KG
	 * @param shape 刚体形状
	 * @return
	 */
	public RigidBody createRigidBody(Object targetObj, double friction, double elasticity, int mass,
			Shape shape) {
		RigidBody rb = new RigidBody(friction, elasticity, mass, shape);
		// rb.setShape(shape);
		// rb.setCoefficientOfFriction(friction);
		rb.setName("RigidBody_" + bodyMap.size());
		bodyMap.put(targetObj, rb);
		collisionDetector.relationGraph.addElement(rb);
		return rb;
	}

	/**
	 * 在物理世界中创建一个活动刚体：能不在外力作用下，自发改变运动速度矢量的刚体。
	 * @param targetObj
	 * @param friction
	 * @param elasticity
	 * @param mass
	 * @param shape
	 * @return
	 */
	public ActiveBody createActiveBody(Object targetObj, double friction, double elasticity, int mass,
			Shape shape) {
		ActiveBody ab = new ActiveBody(friction, elasticity, mass, shape);
		ab.setName("RigidBody_" + bodyMap.size());
		bodyMap.put(targetObj, ab);
		collisionDetector.relationGraph.addElement(ab);
		return ab;
	}
	
	/**
	 * 销毁刚体。Destroy the rigid body in the world.
	 * @param rb
	 */
	public void destroyRigidBody(RigidBody rb) {
		for (Iterator it = bodyMap.keySet().iterator(); it.hasNext();) {
			Object key =  it.next();
			if(rb == bodyMap.get(key)) {
				bodyMap.remove(key);
				break;
			}
		}
		collisionDetector.relationGraph.removeRelation(rb);
	}

	/**
	 * 排除永不碰撞的刚体间的碰撞检测。
	 * @param rb1
	 * @param rb2
	 */
	public void avoidCollision(RigidBody rb1, RigidBody rb2) {
		collisionDetector.relationGraph.removeRelation(rb1, rb2);
	}

	/**
	 * 开始运行物理引擎，直到pause或者stop方法被调用。
	 * @param callback 
	 */
	public void play(final EngineCallback callback) {

		// Create timer task to simulate frames and run in a single thread.
		task = new TimerTask() {
			boolean frameCompleted = true;
			long lastFrameTime = 0;
			int fps = 0;
			long frameTimePeriod;
//			LongHolder frameTimePeriodHolder = new LongHolder(0);

			@Override
			public void run(final long time) {
				// Sleep to test non-full FPS;
//				long random = (long) Calendar.getInstance().getTimeInMillis() % (MIN_FRAME_TIME * 2);
//				try {
//					Thread.currentThread().sleep(random);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}

				frameTimePeriod = time - lastFrameTime;
//				frameTimePeriodHolder.target = frameTimePeriod;

				// Reset FPS counter
				if (time % 1000 == 0) {
					try {
						callback.onFps(time, fps);
					} catch (Exception e) {
						e.printStackTrace();
					}
					fps = 0;
				}

				// If frame not completed, this piece of time will be part of it.
				if (frameCompleted == false) {
					return;
				}

				fps++;

				// Traverse to detect collisions before calculating forces.
				collisionDetector.relationGraph.traverseRelationSafe(new TraverseCallback() {

					@Override
					public void onEntry(Relation entry) {
						RigidBody rb1 = (RigidBody) entry.getE1();
						RigidBody rb2 = (RigidBody) entry.getE2();
						if (collisionDetector.detectCollision(rb1, rb2, frameTimePeriod)) {

							// Notify collision if not stick with each other.
							if (rb1.isStickyWith(rb2) == false) {
								try {
									callback.onCollision(time, rb1, rb2);
								} catch (Exception e) {
									e.printStackTrace();
								}
							}
						}
					}

					@Override
					public void onComplete() {
					}
					
				});
				
				// Detect collision between rigid body and terrain.
				for (Iterator itTerrain = terrainSet.iterator(); itTerrain.hasNext();) {
					Terrain terrain = (Terrain) itTerrain.next();
					for (Iterator itBody = bodyMap.keySet().iterator(); itBody.hasNext();) {
						Object key = (Object) itBody.next();
						RigidBody rb = bodyMap.get(key);

						// Detect rigid body collide with terrain (even if sticky with each other).
						if (collisionDetector.detectCollision(rb, terrain, frameTimePeriod)) {

							if(rb.isStickyWith(terrain) == false) {
								// Callback to invoker with rigid body and terrain.
								try {
									callback.onCollision(time, rb, terrain);
								} catch (Exception e) {
									e.printStackTrace();
								}
							}
							else {
//								try {
//									callback.onStick(rb, terrain);
//								} catch (Exception e) {
//									e.printStackTrace();
//								}
							}
						}
					}
				}

				// Calculate velocity by forces on all rigid bodies.
				for (Iterator it = bodyMap.keySet().iterator(); it.hasNext();) {
					RigidBody rb = bodyMap.get(it.next());
					if(rb.isMovable()) {
						// 因为没有斜坡，Y轴摩擦力暂时不考虑
						// Y-axis forces.
						double v0 = rb.getV().getSpeedy();
						rb.getV().setSpeedy(v0 + gravityAccelaration * (frameTimePeriod / 1000.0));
					}
//					System.out.println(rigidBody.getV());
				}

				// Calculate and transform position in this frame for all rigid body 
				for (Iterator it = bodyMap.keySet().iterator(); it.hasNext();) {
					RigidBody rb = bodyMap.get(it.next());
					if(rb.isMovable()) {
						rb.transform(frameTimePeriod);
					}
				}

//				System.out.println("onFrame: " + bodyMap.size() + " rigid bodies");
				try {
					callback.onFrame(time, bodyMap);
				} catch (Exception e) {
					e.printStackTrace();
				}

				frameCompleted = true;
				lastFrameTime = time;
			}

		};
		timer.runAtFixedRate(task, MIN_FRAME_TIME);
	}
	
	/**
	 * 时间似乎停止了。
	 */
	public void pause() {
		timer.pauseTimer();
		this.pausedTime = Calendar.getInstance();
		System.out.println("The world has been paused.");
	}
	
	/**
	 * 时间停止若干毫秒。
	 * @param timeout Timeout in millisecond.
	 */
	public void pause(long timeout) {
		timer.pauseTimer(timeout);
		this.pausedTime = Calendar.getInstance();
		System.out.printf("The world has been paused for %f seconds", timeout/1000.0);
	}
	
	/**
	 * 时间恢复了。
	 */
	public void resume() {
		timer.resumeTimer();
		double pausedSeconds = (Calendar.getInstance().getTimeInMillis() - this.pausedTime.getTimeInMillis())/1000.0;
		System.out.printf("The world resumed to play, %f seconds paused.", pausedSeconds);
	}
	
	/**
	 * 时间真的停止了。
	 */
	public void stop() {
		timer.stopTimer();
		System.out.println("The world is stoped");
	}

	/**
	 * 重力加速度。
	 * @return
	 */
	public double getGravityAccelaration() {
		return -gravityAccelaration;
	}

	/**
	 * 设置重力加速度。
	 * @param gravityAccelaration 大于等于0的值，默认值为9.8
	 */
	public void setGravityAccelaration(double gravityAccelaration) {
		this.gravityAccelaration = -gravityAccelaration;
		collisionDetector.afterCollision.gravityAccelaration = this.gravityAccelaration;
	}

	/**
	 * 弹性与非弹性碰撞速度临界阈值。
	 * @return
	 */
	public double getSpeedValve() {
		return speedValve;
	}

	/**
	 * 设置弹性与非弹性碰撞速度临界阈值，增大此值可以避免刚体很容易粘滞。
	 * @param speedValve
	 */
	public void setSpeedValve(double speedValve) {
		this.speedValve = speedValve;
		collisionDetector.afterCollision.speedValve = speedValve;
	}

	/**
	 * 是否在运行。
	 * @return
	 */
	public boolean isRunning() {
		return timer.getStatus() == TimerThread.STATUS_RUNNING;
	}
	
	/**
	 * 获得运行状态(TimerThread.STATUS_RUNNING, TimerThread.STATUS_PAUSED, TimerThread.STATUS_STOPED)。
	 * @return
	 */
	public int getStatus() {
		return timer.getStatus();
	}
	
	public Set<Terrain> getTerrains() {
		return terrainSet;
	}


}
